#include "components/shell.h"

#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>

#include "sys_log.h"
#include "xz_dec.h"

typedef struct
{
    char filename[CONFIG_SH_MAX_LINE_LEN];
    int fd;
    void *sh_hdl;

} xz_io_hdl_t;

static int _xz_in(void *hdl, void *buf, int in_pos, int size);
static int _xz_out(void *hdl, int out_pos, const void *buf, int size);

SHELL_CMD_FN(_exec_xz_enc);
SHELL_CMD_FN(_exec_xz_dec);
SHELL_CMD_CP_FN(_exec_xz_param);

SHELL_REGISTER_CMD(
    register_xz_command,
    SHELL_SETUP_CMD("xz-enc", "Execute the 'xz' command of the system to compress files", _exec_xz_enc, _exec_xz_param), //
    SHELL_SETUP_CMD("xz-dec", "Execute the 'xz' API to decompress file", _exec_xz_dec, _exec_xz_param),                  //
);

typedef enum
{
    _SCAN_TYPE_DIR,
    _SCAN_TYPE_FILE,
} scan_type_t;

static void _completion_path(sh_t *sh_hdl, int argc, const char *argv[], bool flag, scan_type_t type); // sh 专用，执行目录/文件自动补全
static void _split_path(const char **path, const char **arg_str, char buf[CONFIG_SH_MAX_LINE_LEN], const char *argv);
static void _scan_resource(sh_t *sh_hdl, const char *path, scan_type_t type, const char *arg_str);

SHELL_CMD_FN(_exec_xz_enc)
{
    if (argc == 1)
    {
        char buf[200 + CONFIG_SH_MAX_LINE_LEN]; // xz 命令和参数
        SYS_SPRINT(buf,
                   "xz -f -k "
                   "--no-sparse "
                   "--armthumb "
                   "--check=none "
                   "--lzma2=preset=6,dict=8KiB,lc=3,lp=1,pb=1 %s",
                   argv[0]);

        shell_print(sh_hdl, "xz encode start '%s' ==> '%s.xz' ...\r\n", argv[0], argv[0]);
        int ret = system(buf); // 通过 shell 执行命令
        shell_print(sh_hdl, "\r");
        if (ret == 0)
        {
            struct stat file_state_in;
            struct stat file_state_out;
            strcpy(buf, argv[0]);
            stat(buf, &file_state_in);
            strcat(buf, ".xz");
            stat(buf, &file_state_out);
            shell_print(sh_hdl, "xz encode end, size %u --> %u\r\n", file_state_in.st_size, file_state_out.st_size);
        }

        return ret;
    }
    else
    {
        shell_print(sh_hdl, "Usage: bash <command>\r\n");
        return -1;
    }
}

SHELL_CMD_FN(_exec_xz_dec)
{
#define XZ_DEC_INBUF_SIZE (1024 * 2)
#define XZ_DEC_OUTBUF_SIZE (1024 * 4)
#define XZ_DEC_DICT_MAX (1024 * 32)

    int ret = -1;

    if (argc)
    {
        xz_io_hdl_t xz_in_hdl;
        xz_io_hdl_t xz_out_hdl;

        strcpy(xz_in_hdl.filename, argv[0]);
        strcpy(xz_out_hdl.filename, xz_in_hdl.filename);
        strcat(xz_out_hdl.filename, ".dec");

        /* 打开和创建输入输出文件 */
        do
        {
            xz_in_hdl.fd = open(xz_in_hdl.filename, O_RDONLY);
            if (xz_in_hdl.fd < 0)
            {
                shell_log_err(sh_hdl, "open file fail\r\n");
                return -1;
            }

            remove(xz_out_hdl.filename);
            xz_out_hdl.fd = open(xz_out_hdl.filename, O_CREAT | O_RDWR, 0777);
            if (xz_out_hdl.fd < 0)
            {
                close(xz_in_hdl.fd);
                shell_log_err(sh_hdl, "create file fail\r\n");
                return -1;
            }

        } while (0);

        /* 执行解压文件 */
        ret = xz_decode(_xz_out, &xz_out_hdl, _xz_in, &xz_in_hdl);
        switch (ret)
        {
        case -1:
        {
            shell_log_err(sh_hdl, "no memory\r\n");
            break;
        }
        case -2:
        {
            remove(xz_out_hdl.filename);
            shell_log_err(sh_hdl, "xz_dec_run() fail\r\n");
            break;
        }
        case -3:
        {
            remove(xz_out_hdl.filename);
            shell_log_err(sh_hdl, "file io failed.\r\n");
            break;
        }
        default:
            break;
        }

        /* 关闭输入输出文件 */
        do
        {
            close(xz_in_hdl.fd);
            close(xz_out_hdl.fd);
        } while (0);
    }
    else
    {
        shell_print(sh_hdl, "Usage: bash <command>\r\n");
    }

    return ret;
}

SHELL_CMD_CP_FN(_exec_xz_param)
{
    if (argc + flag < 2)
    {
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_DIR);
        _completion_path(sh_hdl, argc, argv, flag, _SCAN_TYPE_FILE);
    }
}

/**
 * @brief sh 专用，执行 linux 系统下的 目录、文件名 的自动补全
 *
 * @param sh_hdl 原参数
 * @param argc 原参数
 * @param argv 原参数
 * @param flag 原参数
 * @param type 被全目标类型为目录或文件名
 */
static void _completion_path(sh_t *sh_hdl, int argc, const char *argv[], bool flag, scan_type_t type)
{
    if (argc == 0 || flag)
    {
        _scan_resource(sh_hdl, "", type, "");
    }
    else
    {
        char buf[CONFIG_SH_MAX_LINE_LEN];
        const char *path;
        const char *arg_str;

        _split_path(&path, &arg_str, buf, argv[argc - 1]);
        _scan_resource(sh_hdl, path, type, arg_str);
    }
}

/**
 * @brief 内部的 _completion_path() 专用
 * 为 _scan_resource() 执行参数的预处理。
 * 根据当前命令行正在输入的参数，以 '/' 为分隔符，
 * 解析出 _scan_resource() 所需求 path 和 arg_str
 *
 * @param path[out] 根据当前命令行正在输入的参数，以 '/' 为分隔符，指向当前的目录名
 * @param arg_str[out] 指定已输入的待自动补全的字符串，当为NULL时表示按当前命令行自动填充
 * @param buf 需要一长度为 CONFIG_SH_MAX_LINE_LEN 字节的缓存
 * @param argv 当前命令行中正在输入的参数
 */
static void _split_path(const char **path, const char **arg_str, char buf[CONFIG_SH_MAX_LINE_LEN], const char *argv)
{
    strcpy(buf, argv);

    char *p = strrchr(buf, '/');
    if (p == NULL)
    {
        *path = "";
        p = buf;
    }
    else
    {
        *path = buf;
        *p = '\0';
        p = &p[1];
    }
    *arg_str = p;
}

/**
 * @brief 内部的 _completion_path() 专用。
 * 根据当前已输入的参数 path 和 arg_str，
 * 扫描 linux 系统下的 目录或文件名，
 * 执行用于 sh 的补全过程。
 *
 * @param sh_hdl 原参数
 * @param path 当前命令行正在输入目录名
 * @param type 被全目标类型为目录或文件名
 * @param arg_str 指定已输入的待自动补全的字符串，当为NULL时表示按当前命令行自动填充
 */
static void _scan_resource(sh_t *sh_hdl, const char *path, scan_type_t type, const char *arg_str)
{
    if (*path != '\0')
    {
        /* 确认目录是否存在 */
        DIR *test = opendir(path);
        if (test)
            closedir(test);
        else
            return;
    }

    /* 列出 linux 的文件系统下，指定目录下的所有目录或文件名，并输入到 sh_completion_resource() */
    do
    {
        char buffer[CONFIG_SH_MAX_LINE_LEN] = "ls ";
        strncat(buffer, path, sizeof(buffer) - 1);
        if (type == _SCAN_TYPE_DIR)
            strncat(buffer, " -a1 -F | grep [/$]", sizeof(buffer) - 1);
        else
            strncat(buffer, " -a1 -F | grep -v [/$]", sizeof(buffer) - 1);

        FILE *fp = popen(buffer, "r");
        for (;;)
        {
            char *p = fgets(buffer, sizeof(buffer), fp);
            if (p)
            {
                char *lf = strrchr(p, '\n');
                if (lf)
                {
                    lf[0] = '\0'; /* 删除结尾的 '\n' */
                }
                if (type == _SCAN_TYPE_DIR)
                {
                    if (p[0] == '.')
                    {
                        continue;
                    }
                }
                else
                {
                    p[strlen(p) - 1] = ' ';
                }
                sh_completion_resource(sh_hdl, arg_str, p, NULL); /* 给出可供查找的提示符 */
            }
            else
            {
                break;
            }
        }
        pclose(fp);

    } while (0);
}

static int _xz_in(void *hdl, void *buf, int in_pos, int size)
{
    xz_io_hdl_t *io_hdl = hdl;
    return read(io_hdl->fd, buf, size); // 读取源文件数据
}

static int _xz_out(void *hdl, int out_pos, const void *buf, int size)
{
    xz_io_hdl_t *io_hdl = hdl;
    return write(io_hdl->fd, buf, size); // 输出解压后的文件数据
}
